package gallery
{
    import flash.geom.Rectangle;
    import flash.geom.Point;
    import flash.display.Sprite;
    import flash.display.MovieClip;
    import flash.display.DisplayObject;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.events.TimerEvent;
    import flash.utils.Timer;

    import org.kolonitsky.alexey.events.ListEvent;

    import caurina.transitions.Tweener;

    //TODO: Create loading status for slots

    /**
     *  Dispatched when user select item from list. This event carry
     *  selected item index.
     */
    [Event(name="select", type="org.kolonitsky.alexey.events.ListEvent")]

    /**
     *  SlotList is a list with images. Each image loaded in it place (slot)
     */
    public class SlotList extends Sprite
    {
	/**
	 *
	 */
	public static const SLOT_HEIGHT:Number = 50;

	/**
	 *
	 */
	public static const SLOT_WIDTH:Number = 50;

	/**
	 *  Gap bitween two images
	 */
	public static const GAP:Number = 10;

	/**
	 *  Scroll distance is a distance to list bound for begin
	 *  scrolling
	 */
	public static const SCROLL_DISTANCE:Number = 100;

	/**
	 *  Horizontal orientation mean that items in list will displayed
	 *  as horizontal sequence. Line.
	 */
	public static const ORIENTATION_HORIZONTAL:int = 1;

	/**
	 *  Vertical orientation mean that items in list will displayed
	 *  as vertical sequence
	 */
	public static const ORIENTATION_VERTICAL:int = -1;



	//----------------------------------------------------------------------
	//
	//  Properties
	//
	//----------------------------------------------------------------------

	//--------------------------------------
	//  position
	//--------------------------------------

	private var _position:Rectangle = null;

	/**
	 * position is a property with collection of files for
	 * positioning component.
	 */
	public function get position ():Rectangle
	{
	    return _position;
	}

	public function set position(value:Rectangle):void
	{
	    _position = value;
	    layout(true);
	}


	//--------------------------------------
	//  orientation
	//--------------------------------------

	/**
	 *  Orientation is a flag indicate which side longer
	 *
	 *  @return 1 if top size longer, -1 if left side longer
	 *  0 if top side and left side is equal
	 */
	public function get orientation():int
	{
	    var result:int = ORIENTATION_HORIZONTAL;
	    var leftSide:int = position.bottom - position.top;
	    var topSide:int = position.right - position.left;
	    result = leftSide > topSide ? ORIENTATION_VERTICAL : result;
	    result = leftSide < topSide ? ORIENTATION_HORIZONTAL : result;
	    return result;
	}


	//--------------------------------------
	//  scrollable
	//--------------------------------------

	/**
	 *  Scrollable flag indicate that list can be scrolled. This flag set
	 *  if list conain more items then can be displaed.
	 *
	 *  @return TRUE if list scollable FALSE if all items shown and list
	 *  can't be scrolled
	 */
	public function get scrollable():Boolean
	{
	    var slotsSize:Number = orientation == ORIENTATION_HORIZONTAL ? 0 : listHeight;
	    var listSize:Number = orientation == ORIENTATION_HORIZONTAL ? position.width : position.height;
	    var result:Boolean = listSize < slotsSize;
	    return result;
	}




	//---------------------------------------------------------
	//
	// Methods
	//
	//---------------------------------------------------------

	/**
	 * @constructor
	 */
	public function SlotList(slotCount:int=1)
	{
	    this.slotCount = slotCount;
            
	    addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);

	    scrollTimer = new Timer(100);
	    scrollTimer.addEventListener(TimerEvent.TIMER, scrollTimer_timerHandler);
	}


	/**
	 *  Put image to slot
	 */
	public function putImage(index:int, image:DisplayObject):void
	{
            slots[index].updateItem(image);
            layout();
	}




	//-----------------------------------------------------------
	//
	// Private logic
	//
	//-----------------------------------------------------------

	private var slotCount:int = -1;
        private var slots:Array /* of SlotItem */ = null;
        private var slotFrames:Array /* of SlotFrames */ = null;
	private var slotContainer:Sprite = null;


	/**
	 *  Apply new position. This function redraw component and
	 *  apply new clip window.
	 */
	private function layout(redraw:Boolean=false):void
	{
	    x = position.x;
	    y = position.y;
        if (redraw)
        {
            graphics.clear();
            graphics.beginFill(0x222222);
            graphics.drawRect(0, 0, position.width, position.height);
        }

	    if (slotContainer)
        {
	        slotContainer.scrollRect = new Rectangle(0, 0, position.width, position.height);
            var slot:SlotItem = slots[0];
            var xPos:Number = slot.x;
            var yPos:Number = slot.y;
            for (var i:int = 0; i < slotCount; i++)
            {
                slot = slots[i];
                slot.x = xPos;
                slot.y = yPos;

                xPos += orientation == ORIENTATION_HORIZONTAL ? slot.width + GAP : 0;
                yPos += orientation == ORIENTATION_VERTICAL ? slot.height + GAP : 0;

            }

            moveSlotItems(0, 0);
        }
	    }

        private function addSlots():void
        {
            slots = new Array(slotCount);
      	    slotContainer = new Sprite();
            addChild(slotContainer);
                       
            for (var i:int = 0; i < slotCount; i++)
            {
	        var slot:SlotItem = new SlotItem(i, SLOT_WIDTH, SLOT_HEIGHT);
	        slot.index = i;
	        slot.x = GAP + SLOT_WIDTH / 2;
	        slot.y = i * (SLOT_HEIGHT + GAP);

                slots[i] = slot;
            }
        }


        //----------------------------------------------------------------------
        // Infinity scroll 
        //----------------------------------------------------------------------

        private function moveSlotItems(dx:Number, dy:Number):void
        {
            var viewPort:Rectangle = slotContainer.scrollRect;
            if (viewPort == null)
                return;
            
            offsetSlots(dx, dy);

            var finalXOffset:Number = 0;
            var finalYOffset:Number = 0;

            for (var i:int = -slotCount; i < slotCount + slotCount; i++)
            {
                var sb:Rectangle = getSlotBoundary(i);
                var slot:SlotItem = getSlotByIndex(i)

                if (viewPort.intersects(sb))
                {
                    trace("INFO: ADD i" + i + " s:" + slot + " b:" + sb);
                    
                    if (i == -slotCount)
                        finalYOffset += listHeight;

                    if (i == slotCount + slotCount - 1)
                        finalYOffset -= listHeight;


                    if (slot && !slotContainer.contains(slot) )
                        slotContainer.addChild(slot);
                }
                else
                {
                    trace("INFO: REM i" + i + " s:" + slot + " b:" + sb);

                    if (slot && slotContainer.contains(slot))
                        slotContainer.removeChild(slot);
                }
            }

            offsetSlots(finalXOffset, finalYOffset);
        }

        private function offsetSlots(dx:Number, dy:Number):void
        {
            if (dx == 0 && dy == 0)
                return;

            for (var i:int = 0; i < slotCount; i++)
            {
                slots[i].x += dx;
                slots[i].y += dy;
            }
        }

        private function getSlotByIndex(index:int):SlotItem
        {
            index = (index + slotCount) % slotCount;
            return slots[index];
        }

        private function getSlotBoundary(index:int):Rectangle
        {
            var s:SlotItem = getSlotByIndex(index);
            var result:Rectangle = new Rectangle(s.x, s.y, s.width, s.height);
            if (index < 0)
                result.offset(-0, -listHeight);

            if (index >= slotCount)
                result.offset(0, listHeight);
            
            return result;
        }

        private function get listHeight():Number
        {
            var last:int = slotCount - 1;
            var yMin:Number = slots[0].y;
            var yMax:Number = slots[last].y + slots[last].height;
            var result:Number = yMax - yMin;
            return result;
        }

	private function addedToStageHandler(event:Event):void
	{
	    addEventListener(MouseEvent.MOUSE_OVER, mouseOverHnadler);
	    addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);

            addSlots();
	    layout(true);
            moveSlotItems(0, 0);
	}
        
        
	//------------------------------------------------------------------
	// scrolling logic
	//------------------------------------------------------------------

	private function mouseOverHnadler(event:MouseEvent):void
	{
	    trace("mouseOverHandler: " + scrollable);
	    if (scrollable)
	        stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
	}

	private function mouseOutHandler(event:MouseEvent):void
	{
	    trace("mouseOutHandler: " + scrollable);
	    if (scrollable)
	        stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
	    scrollTimer.stop();
	}
        

	//------------------------------------------------------------------
	// scrolling logic
	//------------------------------------------------------------------

	private var scrollTimer:Timer = null;
	private var scrollX:int = 0;
	private var scrollY:int = 0;

	private function mouseMoveHandler(event:MouseEvent):void
	{
	    var cp:Point = new Point(event.stageX, event.stageY);
	    scrollX = 0;
	    scrollY = 0;

	    if (orientation == ORIENTATION_HORIZONTAL)
	    {
		scrollX = cp.y - position.right < SCROLL_DISTANCE ? -1 : scrollX;
		scrollX = position.left - cp.x < SCROLL_DISTANCE ? 1 : scrollX;
	    }

	    if (orientation == ORIENTATION_VERTICAL)
	    {
		scrollY = cp.y - position.top < SCROLL_DISTANCE ? -1 : scrollY;
		scrollY = position.bottom - cp.y  < SCROLL_DISTANCE ? 1 : scrollY;
	    }

	    if (scrollX == 0 && scrollY == 0)
	        scrollTimer.stop();
	    else
	        scrollTimer.start();
	}

	private function scrollTimer_timerHandler(event:TimerEvent):void
	{
            moveSlotItems(scrollX, scrollY);
	}
    }
}